06 - Embedded Programming

Programming a Microcontroller

With this weeks topic, the second lecture of a series of lectures about electronics is started. The previous was electronics production, where we partially designed but mainly produced our own PCB by milling, stuffing and soldering. This week was about programming the mircocontroller on the board we made.

This weeks's assignment were:

  • Group Assignment
    • Browse through the data sheet for your microcontroller
    • Compare the performance and development workflows for other architectures
  • Individual Assignment
    • Write a program for a microcontroller development board that you made ...
      • ... to interact (with local input and/or output devices)
      • ... and communicate (with remote wired and/or wireless devices)
    • For extra credit use different languages and/or development environments
    • For extra credit connect external components to the board

Group Assignment

For the group assignment, we each read the datasheet for our and then compared the performance of development workflows to other architectures. You can find our results here.

For our PCBs we used the Seeed Studio XIAO Seeeduino. It does not have a data sheet (only a user manual) but supplies most of the necessary information in a wiki entry. It carries a powerful microcontroller, namely ATSAMD21-G18A-MU. For this, a data sheet is available online via this link. Therefore, we did not only carefully read the wiki entry but also the data sheet for the microcontroller.

When opening the data sheet for the microcontroller, I was surprised that the number of pages exceedes one thousand. Apparently, this is quite common according to my instructor. However, reading all of the pages would not only be very time intensive but also might not yield wanted information. Therefore, it is important to filter and only read the sections about some key features. Below, I have summarized which specifications should be known, the reason(s) for it and what information I acquired from it for the ATSAMD21-G18A-MU. In addition to these, I highly recommend to read the section about the features, the description and the configuration summary in the very beginning as they supply the reader with many essential information on few pages.

Section Information Details for ATSAMD21-G18A-MU
Schematics/Functional Block Diagram Architecture, components, instances and functions of pins (Just a general overview)
Pinout Pin implementations and functions - Which pins are available? E.g. input vs. output, analog vs. digital, power/ground, serial communication, 38 general purpose inputs/outputs (GPIOs)
Pin Multiplexing Options for selecting a single function for a single physical pin - What function can the pin have? Selection in Software e.g. for pin number 8: input/output port B, powered with voltage level "VDDANA", external interrupt, analog input, analog PTC (Peripheral Touch Controller) input, serial communication, waveform output of time counter
Power Supply Power supply to microcontroller, supply of pins Operating voltage from 1.62V to 3.63V
Memories Size of memory, computing velocity 256KB Flash, 32KB SRAM, 64 KB peripherals, 32-bit
PM - Power Manager Power modes with different active or stopped processes Power mode (active) and two sleep modes (IDLE with 3 levels and STANDBY)
SERCOM - Serial Communication Interface Available communication protocols I2C, USART, SPI
ADC, AC and DAC ADC resolution, conversion rate, conversion range ADC resolution: 8-, 10- or 12-bit (default); DAC resolution: 10-bit; conversion rate: 350 kilo samples per second (kps); DAC conversion between ground and Vref
Electrical Characteristics Clock frequency, current consumption 48 MHz; Consumption in IDLE mode 1.17 mA in Active IN 10.3 mA
Packaging Information Package size, pin size + distance, thermal resistance of package, soldering profile 1.2 x 9 x 9 mm package, 0.25 mm pin width, 0.5 mm distance, 32°C/W, max. soldering temperature of 260°C

The Seeed Studio XIAO Seeeduino however only carries the ATSAMD21-G18A-MU microcontroller but also adds another board layout on top which changes some properties. Most importantly, the GPIOs are narrowed down to eleven, all of which can be used as analog or digital input or outputs. In addition, some of these pins can be used for serial communication either for I2C (SDA & SLA), for USART (Rx & Tx) and SPI (MISO, MOSI & SCK). Furthermore, there is one pin capable of digital to analog conversion. Lastly, several pins can be used to sense minor capacity changes e.g. in case of touch, called QTouch.

In addition to the GPIOs, there are three other pins, one of them supplies ground, and the other two a power of 5V and 3.3V, respectively. The detailed pinout of the Seeed Studio XIAO Seeeduino is shown below.

Pinout of Seeed Studio XIAO Seeeduino

Due to the additional layout, the board has slightly increased in size and now has the dimensions of 20 × 17.5 × 3.5 mm. Additionally, the microcontroller can now be connected to a device via USB Type-C.

As a last important aspect, the reset pins should be mentioned. In case they are short circuited twice, shortly behind each other, the chip enters the so called "Bootloader Mode". The Seeeduino has two partitions, one is for the the actual program coded by the user and the other for the bootloader. By entering the bootloader mode, the second partition can be accessed. This is useful in case a program has failed. In addition, in case the reset pins are short circuited only once, the first partition is reset.

Individual Assignment

The individual assignment consisted of programming a board to make it firstly interact with local input and output devices and secondly to communicate with a remote device. I had done both of this before, actually several times. For exampled, I have programmed an Arduino Nano to read the analog data of a strain gauge and a load cell which is sent to a computer via serial communication where the data is saved along with a time tag of the data recoded.

However, just repeating steps and documenting them here would be a boring assignment with nothing to learn. Therefore, I wanted to challenge myself a bit and use a different programming language and environment. I chose Python as the language and Microsoft's Visual Studio Code, which I have both used before. Python, in my eyes, is quite a lot easier to program than C/C++ but can consume too much memory. Luckily, the microcontroller that I have soldered to my PCB is capable of Python.

Interacting with Input and Output Devices

Interacting with local input and output devices is not really challenging for me. There are analog and digital devices. Digital input and outputs only have two states, namely one or zero which represent either a low or a high voltage level, respectively. What a low and high voltage level depends on the voltage used to power the microcontroller. Analog signals in contrast can take any value between the low and high voltage level and by this have many different states. The number of these states is given by the resolution of the analog signal, which is specific to the microcontroller.

For this assignment, I will be using the button as an input device and the LEDs on the PCB and the microcontroller as an output device to interact with. The button is a digital device. The pin which reads the state of the button, namely the "D0" pin, is connected to 5V via a pull-up resistor. Only if it is pressed, the pin is connected to the ground. Therefore, the button is active when a low voltage is read at the pin.

Also, the LEDs are digital devices. In contrast to the button, the LEDs need a high voltage for being activated. One exception however is the built-in LED of the microcontroller I am using on my PCB, the Seeed Studio XIAO SAMD21. It actually works vice versa according to its data sheet.

Embedded Programming with Arduino IDE

One way, probably the most popular method of programming the SamD21 microcontroller is to use the Arduino IDE as it is the go-to method described in the wiki entry of the Seeed Studio XIAO SAMD21. In this section of the wiki entry, it is described how to get started by using the Arduino IDE.

For a previous assignment, i.e. in week four electronics production during the test phase, I have already programmed the SAMD21. I used the Arduino IDE to uploaded basic scripts to the SAMD21 to test its functionality.

To setup the Arduino IDE and use it to program the SAMD21, I simply followed the steps that are described here on the wiki website in the "Getting Started" section. Furthermore, I uploaded two scripts to the microcontroller. The first turns on an LED, waits a second, turns it off and waits another second until it is repeated. The second script turned the LED on every time the button is pressed. Both scripts worked right away without any problems. For a more detailed documentation about the test phase using the Arduino IDE, please refer to this section.

Embedded Programming with CircuitPython

As I had done embedded programming using the Arduino IDE already previously, in fact many times, I wanted to challenge myself to learn something new. Here, a friend who did FabAcademy two years ago proposed to use Python. I immediately was really excited about it as I love Python. It makes coding a lot easier, at least in my eyes. Therefore, I started right away and looked for instructions on how to get started with it. I found a link on the wiki entry about how to get started with CircuitPython leading to this website.

For using Python as a programming language to program the microcontroller, I firstly had to download the bootloader (.uf2) of CircuitPro for the Seeed Studio XIAO SAMD21 from here. Then, I plugged the SAMD21 into my computer and made it enter the bootloader mode by short connecting the reset (RST) pins twice using a male to male jumper cable.

Entering Bootloader Mode

Immediately after this, my computer made the sound of a disconnecting external drive. In fact, an external drive appeared called "Arduino (D:)".

"Arduino (D:)" as New Device

Next, I copied the previously downloaded bootloader file and pasted it onto the external drive "Arduino (D:)". Just after pasting the file, my computer made the disconnecting sound again followed by a connecting sound. The previously called "Arduino (D:)" was now called "CIRCUITPY (D:)". It now had a lot less memory available.

"CIRCUITPY (D:)" as New Device

In addition to a new name, the external device called "CIRCUITPY (D:)" also had new files on it. Here, I immediately recognized the code.py file as a script coded in the programming language Python. Any code that I want to upload to the microcontroller can now be simply saved on "CIRCUITPY (D:)" with the name code.py.

Files in "CIRCUITPY (D:)"

After having completed these steps, it is recommended on the website with the instructions to upload the code shown below to the microcontroller to check wether CircuitPython is functional. For this, I opened the code.py file with VS Code and pasted the code into the editor. Then, I only added some comments with "#" to explain what the code is doing in a specific line and saved the file by pressing Ctrl + S.

import time
import board
from digitalio import DigitalInOut, Direction

# Declare and initialize led pin
led = DigitalInOut(board.LED_INVERTED)
# Set led pin to output
led.direction = Direction.OUTPUT

while True:
	led.value = True	# Turn led off LED (INVERTED!)
	time.sleep(1)		# Wait 1s
	led.value = False	# Turn led on
	time.sleep(1)		# Wait 1s

This code basically does the very same thing as the first code of testing using the Arduino IDE. It starts with initializing the pin of the LED, setting it up as an output and finally giving it the value "True" and "False" alternatingly with a pause of one second between the switches. By this, the LED is turned on and off for a second, each.

After saving this code and by this uploading it to the microcontroller, I noticed that this affected the built-in LED on the microcontroller. Hence, I changed led = DigitalInOut(board.LED_INVERTED) to led = DigitalInOut(board.D8) in the code and saved it again. By this, the SAMD21 was now blinking exactly as shown for the first script programmed in the Arduino IDE for testing my own board.

As a second script, I wanted to imitate the behavior of the second Arduino script that I used for testing, namely turning the led on every time the button is pressed and off when the button is not pressed. I am very familiar with all kinds of programming languages, including Python, and therefore it was very easy for me to achieve it. This is the code I programmed:

import board
from digitalio import DigitalInOut, Direction

# Declare and initialize led and button pins
led = DigitalInOut(board.D8)
button = DigitalInOut(board.D0)

# Set led pin to output and button pin to input
led.direction = Direction.OUTPUT
button.direction = Direction.INPUT


while True:
	if button.value == True:
		# if button is not pressed (INVERTED)
		led.value = False
	else:
		# if pressed
		led.value = True

The only thing I had to keep in mind while programming is that the "True" state of the button refers to it not being pressed not pressed. "True" here is equivalent to "HIGH" in the Arduino IDE meaning a high voltage level for digital pins. In contrast, "False" or "LOW" are present for low voltage levels.

After saving this code on the SAMD21, the LED was on every time the button was pressed and off in case it was not, just as expected.

However, I also wanted to program a script that is more challenging. A common aspect of using embedded programming is using a counter, which I wanted to imply as well. Therefore, my goal for this script was to make the LED blink as many times as the button has been pressed previously.

For this, I needed an integer acting as a counter to keep track of how many times, a button is pressed. In case, the button is pressed, the counter needs to be advanced by one. To code it, I started with the previous script where I deleted the else statement and set the if contition to "False" instead of "True". Additionally, I declared and initialized a counter that is advanced inside of the if statement.

Furthermore, after advancing the counter, I programmed a for-loop that is executed as many times as the value of the counter. Inside of the for-loop is a part of the example where the LED blinks continuously, just with a delay decreased to 0.15 s. For this I also had to add "import time" in the top again.

import time
import board
from digitalio import DigitalInOut, Direction

# Declare and initialize led and button pins
led = DigitalInOut(board.D8)
button = DigitalInOut(board.D0)

# Set led pin to output and button pin to input
led.direction = Direction.OUTPUT
button.direction = Direction.INPUT

# Declare and initialize counter
counter = 0

while True:
	if button.value == False:
		# if button is pressed (INVERTED)
		counter += 1 	# Advance counter
		for i in range(counter):
			# For as many times as the value of counter, do
			led.value = True	# Turn led off LED (INVERTED!)
			time.sleep(0.15)	# Wait 1s
			led.value = False	# Turn led on
			time.sleep(0.15)	# Wait 1s

However, the for loop is entered as soon as the button is pressed. However, I wanted it to be entered when the button is released. Therefore, I added a while loop right before the for-loop that is true as long as the button is pressed. Inside of it, nothing is executed, i.e. there is a sleep statement, and by this, the microcontroller does nothing until the button is released. Only then, the for-loop to blink the LED is entered. This is how the while-loop looks:

while button.value == False:
	time.sleep(0.01)

Next, I wanted to be able to reset the counter. For this, I decided the reset signal was pressing the button for more than a second. Therefore, I needed to be able to tell, how long the button was pressed. From previous experiences, I know that the Arduino IDE has a function called millis(), which returns the number of milliseconds since the begin of the script. Therefore, I looked for the equivalent online, which I found here. In CircuitPython, this function is called time.monotonic(). Therefore, I added two lines of code where the time is saved, namely right after entering the if-statement to capture the time when pressing the button starts and after the while-loop for the end of pressing the button. Then, before the for-loop to blink the LED is entered, the difference between the start and end is calculated. In case it is smaller than one second, the for loop is entered. Otherwise, the counter is simply set to zero. This is how the complete code looks:

import time
import board
from digitalio import DigitalInOut, Direction

# Declare and initialize led and button pins
led = DigitalInOut(board.D8)
button = DigitalInOut(board.D0)

# Set led pin to output and button pin to input
led.direction = Direction.OUTPUT
button.direction = Direction.INPUT

# Declare and initialize counter
counter = 0

while True:
	if button.value == False:
		start = time.monotonic() # Time at start of pressing button
		# if button is pressed (INVERTED)
		counter += 1 	# Advance counter
		while button.value == False:
			time.sleep(0.01)
		stop = time.monotonic() # Time at stopping pressing button   
		if stop-start <= 1:
			# If button was pressed less than a second
			for i in range(counter):
				# For as many times as the value of counter, do
				led.value = True	# Turn led off LED (INVERTED!)
				time.sleep(0.15)	# Wait 1s
				led.value = False	# Turn led on
				time.sleep(0.15)	# Wait 1s
		else: 
			# Reset counter
			counter = 0  

With the code above, I was able to achieve what I wanted, namely the LED blinks as many times as the button was pressed. Furthermore, the counter keeping track of how many times the button was pressed can be reset. Have a look yourself how this looks and works:

LED Blinks as Many Times as Button Was Pressed

Resetting the Counter

Communication with Remote Device

Establishing a serial communication between the host computer and the microcontroller can easily be done using the Arduino IDE as it is very beginner-friendly. I would recommend it for people who are new to embedded programming. However, I have used it several times and it gets quite boring. Nevertheless, I wanted to show to you (and to my forgetful future me), how easily it is done. In addition to that however, I wanted learn something new and use CircuitPython and my all-time favorite programming IDE, Microsoft's Visual Studio Code.

Serial Communication with Arduino IDE

Establishing a serial communication between a microcontroller and the host computer using the Arduino IDE is very easy. It only needs a functional code that includes reactions to incoming serial messages as well as some output. However, I firstly had to switch back to Arduino from CircuitPython. Nevertheless, this was easily done by entering the bootloader mode (see above). Then, the microcontroller was capable of working with the Arduino IDE as the booloader is uploaded with the script.

Then, I started developing the code. As an LED is the only output device available on the board, I manipulated it though the microcontroller. More precisely, I wanted to switch it on in case "1" was sent via the serial communication and switch it of for a "0".

The first step to develop this code was to establish a serial communication by opening a serial stream in Arduino. This is easily done with Serial.begin(). The mircocontroller can furthermore send a string to the serial stream with Serial.print("") or Serial.println(""). Reading a string is done with Serial.readString(). But before it is read, it must be checked whether anything was received with Serial.available().

With these code snippets, I furthermore implemented some logic to turn on and off the led in case the if-conditions were fulfilled. This is the complete code:

// declare and initialize pin for LED
const int ledPin =  D8;// the number of the LED pin

String value;

// the setup function runs once when you press reset or power the board
void setup() {
	// initialize digital pin for LED as an output.
	pinMode(ledPin, OUTPUT);
	
	Serial.begin(9600); // Start serial communication
	while (!Serial); // Wait until Serial is open
	Serial.println("listening..."); // Send that Serial is open and listening
}

// the loop function runs over and over again forever
void loop() {
	if (Serial.available() > 0) {
		// read the incoming string
		value = Serial.readString();
		
		if (value == "1"){
			digitalWrite(ledPin, HIGH);   // turn the LED on
			Serial.println("Message '1' received. Turning LED on.");
		}
		else if (value == "0"){
			digitalWrite(ledPin, LOW);   // turn the LED off
			Serial.println("Message '0' received. Turning LED off.");
		}
		else {
			Serial.print("Unknown message '");
			Serial.print(value);
			Serial.println("'. Use '1' and '0' to turn LED on and off.");
		}
	}
}

Before uploading it to the microcontroller, I selected the right board. The remaining configurations for the board were still the same as set in this section. Then, I opened the serial monitor by clicking on Tools > Serial Monitor or by pressing Ctrl + Shift + M to also capture the moment, the serial stream was opened indicated with the message "listening...".

Then, I uploaded the script to the board. The IDE firstly asked be to save but after specifying the location and name, the sketch compiled and uploaded to the microcontroller.

Serial Monitor of Arduino IDE

With the script being uploaded, I immediately received the message "listening..." on the serial monitor of the Arduino IDE. Then, I started sending messages to the microcontroller by typing in a message in the bar at the top and pressing enter. In case I sent a "1", the LED switched on and for a "0" the LED switched off. Have a look yourself in the video. On the screen you can see the serial monitor and in the front the PCB. Interestingly, every time a serial is received or sent the blue built-in LED is turned on.

LED Switches On and Off According to Serial Input

Serial Communication with VS Code and CircuitPython

After using the Arduino IDE again, I had to configure the microcontroller for CircuitPython once more as I described here. After that, I also configured to setup the programming IDE, VS Code, for a serial communication to a CircuitPython device as unfortunately it is not possible in the default. However, there is an easy tutorial on this website that I followed.

The first step is to download VS Code, which I had done already. Next, I had to install the CircuitPython extension for VS Code. This was of course the newest version v.0.2.0. The third step was to plug-in the PCB into my computer. Then, in VS code, I opened the "CIRCUITPY (D:)" drive as a folder. Here, it is really important to open it as a folder as only then the extension recognizes the code.py file in the workspace to load the environment. With this step, a new folder was created on the drive named ".vscode" where the settings for the workspace are stored in a "settings.json".

From the fifth step on, I was not really able to follow the tutorial anymore. Here, I had to allow dependencies to load but nothing happened. Therefore, I skipped this part. Next was to choose the correct board in the bottom right of the IDE but nothing showed up.

However, it was only for the seventh step, that I really had an issue with this tutorial. I had to go to the command palette with Ctrl + Shift + P where I typed in "CircuitPython". This showed me many possible commands from CircuitPython that I could choose but as soon as I clicked on any, I got an message saying that the command was not found.

Error Message for CircuitPython Commands

Therefore, I looked online for a solution. After a while, I found this post on a GitHub forum. Many solutions were proposed which I tried to follow but nothing worked. After several hours, I was close to giving up but then I was at the very last comment where a person proposed to use a previous version of the extension.

For this, I went to the extensions I had installed for VS code and right-clicked on the CircuitPython extension. Here, I selected "Install Another Version..." and chose the third to last version v0.1.19.

Installing a Previous Version of CircuitPython Extension in VS Code

Immediately, after selecting this version, two messages of VS Code popped up in the bottom left about a bundle downloading and updating. I figured these were the dependencies I had to allow to load in the fifth step of the tutorial. Additionally, I was able to see "Seeed:Seeeduino XIAO" in the right of the status bar, which was potentially the board I had to chose in the sixth step. Lastly, a new terminal called "Circuit Python Serial Monitor" was opened.

Updating Dependencies, Autodetected Board and Serial Monitor

I quickly proceeded with the seventh step of the tutorial to see wether the commands can be found now. Again, pressing Ctrl + Shift + P opened the command palette where I typed in "CircuitPython" and chose the "Select Serial Port" command. I was really happy that no error message showed up but instead, a dropdown with all potential ports appeared. Here, I chose "COM11 Seeed:Seeeduino XIAO".

CircuitPython Command: Select Serial Port

Now, that I was able to use VS Code for serial communication with my board, I looked online for a Python script to let the microcontroller echo he input. It was pretty clear to me that simply using the print() function sends a message from the microcontroller to the host computer. However, I did not know how to send a message from the computer to the microcontroller. Here, I searched for a solution online where I found this post on a forum. Here, they used this code:

import supervisor
print("listening...")

while True:
	if supervisor.runtime.serial_bytes_available:
		value = input().strip()
		# Sometimes Windows sends an extra (or missing) newline - ignore them
		if value == "":
			continue
		print("RX: {}".format(value))

I simply copied and pasted this code into code.py on the CIRCUITPY (D:) drive and pressed save. However, nothing happened and I did not see any "listening..." message in the terminal called "Circuit Python Serial Monitor". I suspected that the monitor might have missed the very first line of the message and therefore pasted the line print("listening...") into the while loop and saved it.

CircuitPython Serial Monitor Before Opening it

However, still nothing happened in the terminal. It was not until I discovered, that I had to open the serial monitor by pressing Ctrl + Shift + P and selecting the command "CircuitPython: Open Serial Monitor". Due to this, the terminal was now flooded with "listening..." messages. I deleted the previously added print() line and saved the file. This stopped the flood of messages, did a soft reboot of the terminal and only printed "listening..." once. In the bottom of the serial monitor, I typed in "can you hear me?" and after pressing enter to send the message to the microcontroller, I got the echo "RX: can you hear me?". Apparently, this code works!

Echoed Message of the Microcontroller via Serial Communication

Then, I started to code my own respond to a message. As there is only one type of output devices, available on the board, the LEDs, I wanted to switch them on and off when the message "1" and "0" is received. To code it, I used the imports except for "time" and the setup for the LED pin from the previous script for blinking the LED connected to the D8 pin. Furthermore, in case the input message which is saved in the variable value is equal to "1" the led should turn with led.value = True and in case it is equal to "0", the led should turn off with led.value = False. This is the complete code:

import supervisor
import board
from digitalio import DigitalInOut, Direction

# Declare and initialize led pin
led = DigitalInOut(board.D8)
# Set led pin to output
led.direction = Direction.OUTPUT

print("listening...")

while True:
	if supervisor.runtime.serial_bytes_available:
		value = input().strip()
		# Sometimes Windows sends an extra (or missing) newline - ignore them
		if value == "":
			continue
		elif value == "1":
			led.value = True
			print("Message '{}' received. Turning LED on.".format(value))
		elif value == "0":
			led.value = False
			print("Message '{}' received. Turning LED off.".format(value))
		else:
			print("Unknown message '{}'. Use '1' and '0' to turn LED on and off".format(value))

I only had one issue coding this script, namely in the if statements. Instead of programming it to be if value == 1, I had to put the number in quotation marks because the input message is a string and not automatically converted to an integer.

After saving it, I was able to switch on the LED by sending the message "1" via the serial monitor and switch it off with "0". In comparison to the communication via the Arduino IDE, the blue built-in LED does not switch on and the the communication is slightly faster as it reacts immediately.

LED Switches On and Off According to Serial Input

Source Code for Download